package com.study.springbootsecurity.filter;

import com.study.springbootsecurity.config.properties.JwtConfigProperties;
import com.study.springbootsecurity.domain.dto.query.LoginDTO;
import com.study.springbootsecurity.entity.LoginUser;
import com.study.springbootsecurity.entity.business.RefreshResponse;
import com.study.springbootsecurity.exception.BusinessException;
import com.study.springbootsecurity.service.impl.TokenService;
import com.study.springbootsecurity.util.*;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseCookie;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.util.AntPathMatcher;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
 * @Description: TODO spring security核心过滤器 登录认证 登录成功 登录失败
 * @author: Huangjianyun
 * @date: 2025-06-06 14:37
 */
public class LoginTokenFilter extends UsernamePasswordAuthenticationFilter {

    private final RedisCache redisCache;

    private final JwtConfigProperties jwtConfigProperties;

    private final RequestBodyUtil requestBodyUtil = new RequestBodyUtil();

    private final TokenService tokenService;
    private final AntPathMatcher pathMatcher = new AntPathMatcher();

    public LoginTokenFilter(AuthenticationManager authenticationManager,RedisCache redisCache,JwtConfigProperties jwtConfigProperties,TokenService tokenService) {
        // 设置认证管理器
        super.setAuthenticationManager(authenticationManager);
        // 设置redis工具类
        this.redisCache = redisCache;
        // 设置jwt配置
        this.jwtConfigProperties = jwtConfigProperties;
        this.tokenService = tokenService;
        // 设置不仅限于post的登录方式;允许其他请求方式登录
        super.setPostOnly(true);
        // 设置登录路径 /api/user/login  请求方式post
        super.setRequiresAuthenticationRequestMatcher(
                PathPatternRequestMatcher.withDefaults()
                        .matcher(HttpMethod.POST,jwtConfigProperties.getLoginPath()));
        super.setUsernameParameter("username");
        super.setPasswordParameter("password");
    }

    /**
     * 处理登录认证请求
     * @param request HTTP请求对象，包含客户端请求信息
     * @param response HTTP响应对象，用于返回认证结果
     * @return 返回认证成功的Authentication对象
     * @throws AuthenticationException 认证失败时抛出异常
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        // 1 获取请求对象用户名和密码
        Authentication authentication;
        try {
            LoginDTO loginDTO = requestBodyUtil.getRequestBody(request, LoginDTO.class);
            // 2创建用户名密码登录令牌对象
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginDTO.getUsername(), loginDTO.getPassword());
            // 3 获取认证管理器 调用认证管理器authenticate方法进行认证
            authentication = this.getAuthenticationManager().authenticate(authenticationToken);
        } catch (IOException e) {
            throw new BusinessException("LoginTokenFilter类的attemptAuthentication方法 请求request对象输入流失败");
        }

        return authentication;
    }
    /**
     * 登录认证成功后的处理
     * @param request HTTP请求对象
     * @param response HTTP响应对象，用于返回认证成功结果
     * @param chain 过滤器链，用于继续处理请求
     * @param authResult 认证成功的Authentication对象
     * @throws IOException 输入输出异常
     * @throws ServletException Servlet处理异常
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        //登录成功 生成访问令牌和令牌存储并存储刷新令牌到redis
        //SecurityContextHolder.getContext().setAuthentication(authResult);
        LoginUser principal = (LoginUser)authResult.getPrincipal();
        // 生成refreshToken的key 并客户端标识绑定
        String deviceFinerPrint = DeviceFinerUtil.buildDeviceFingerprint(request);
        String accessToken = TokenUtil.createToken(principal.getSysUser(), jwtConfigProperties);
        String refreshToken = TokenUtil.createRefreshToken(principal.getUsername(),jwtConfigProperties,deviceFinerPrint);
        // 存储刷新令牌到redis
        String refreshTokenKey = "refresh"+":"+principal.getUsername()+":"+deviceFinerPrint;
        tokenService.saveRefreshToken(refreshTokenKey,refreshToken,jwtConfigProperties.getRefreshExpiration());
        // 存储系统用户类redis 有效期和refreshToken有效期一样 存储用户信息：用户名+":"+设备标识
        redisCache.set("user:info:"+principal.getUsername()+":"+deviceFinerPrint,principal.getSysUser(),jwtConfigProperties.getRefreshExpiration(), TimeUnit.SECONDS);
        // 设置刷新令牌到token
        ResponseCookie.from("refresh_token",refreshToken)
                .httpOnly(true)
                .secure(true)
                .path("/")
                .maxAge(jwtConfigProperties.getRefreshExpiration())// 设置 Cookie 有效期为 7 天（单位：秒）
                .sameSite("Strict")   // 严格限制跨站请求（防御 CSRF 攻击）
                .build();             // 构建 ResponseCookie 对象
        CookieUtil.set(response, "refresh_token", refreshToken,
                jwtConfigProperties.getRefreshExpiration(), // 秒
                "/", // 根路径
                true,
                true);
        // 登录成功 返回刷新令牌和访问令牌
        RefreshResponse refreshResponse = new RefreshResponse();
        refreshResponse.setRefreshToken(refreshToken);
        refreshResponse.setAccessToken(accessToken);
        Result<RefreshResponse> result = Result.success(refreshResponse);
        ResponseUtil.outJson(response,result);
        // 登录成功后 设置refreshToken到cookie

    }

    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        Result<String> result = Result.fail(100001,"登录失败");
        ResponseUtil.outJson(response,result);
    }

}
